#include "Common.h"
#include "OpenGLRenderTarget.h"
#include "OpenGLTexture.h"
#include "Utils/Util.h"
#include <iostream>
#include "../Renderer.h"
#include "App/AppBase.h"

using namespace RayFrame;

bool checkFramebufferStatus()
{
  // check FBO status
  GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
  switch(status)
  {
  case GL_FRAMEBUFFER_COMPLETE_EXT:
    std::cout << "Framebuffer complete." << std::endl;
    return true;

  case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT:
    std::cout << "[ERROR] Framebuffer incomplete: Attachment is NOT complete." << std::endl;
    return false;

  case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT:
    std::cout << "[ERROR] Framebuffer incomplete: No image is attached to FBO." << std::endl;
    return false;

  case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT:
    std::cout << "[ERROR] Framebuffer incomplete: Attached images have different dimensions." << std::endl;
    return false;

  case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT:
    std::cout << "[ERROR] Framebuffer incomplete: Color attached images have different internal formats." << std::endl;
    return false;

  case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT:
    std::cout << "[ERROR] Framebuffer incomplete: Draw buffer." << std::endl;
    return false;

  case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT:
    std::cout << "[ERROR] Framebuffer incomplete: Read buffer." << std::endl;
    return false;

  case GL_FRAMEBUFFER_UNSUPPORTED_EXT:
    std::cout << "[ERROR] Unsupported by FBO implementation." << std::endl;
    return false;

  default:
    std::cout << "[ERROR] Unknow error." << std::endl;
    return false;
  }
}


void OpenGLRenderTarget::Create(Texture* tex)
{
  m_texture = tex;
  m_GLTexture = dynamic_cast<OpenGLTexture*>(tex);
  assert(tex);

  RenderTarget::Create(tex);
}

void OpenGLRenderTarget::internalCreate()
{
  // create a framebuffer object, you need to delete them when program exits.
  glGenFramebuffersEXT(1, &m_fboId);
  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_fboId);

  // create a renderbuffer object to store depth info
  // NOTE: A depth renderable image should be attached the FBO for depth test.
  // If we don't attach a depth renderable image to the FBO, then
  // the rendering output will be corrupted because of missing depth test.
  // If you also need stencil test for your rendering, then you must
  // attach additional image to the stencil attachement point, too.
  glGenRenderbuffersEXT(1, &m_rboId);
  glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, m_rboId);
  glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, m_GLTexture->GetWidth(), m_GLTexture->GetHeight());
  glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0);

  // attach a texture to FBO color attachement point
  glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, m_GLTexture->GetTextureID(), 0);

  // attach a renderbuffer to depth attachment point
  glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, m_rboId);

  //@ disable color buffer if you don't attach any color buffer image,
  //@ for example, rendering depth buffer only to a texture.
  //@ Otherwise, glCheckFramebufferStatusEXT will not be complete.
  //glDrawBuffer(GL_NONE);
  //glReadBuffer(GL_NONE);

  // check FBO status
  m_isOK = checkFramebufferStatus();
  if(!m_isOK)
    ErrorMsg("Error while creating FBO");

  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
}

void OpenGLRenderTarget::internalDestroy()
{
  assert(m_isOK && m_fboId && m_rboId);

  if(! (m_isOK && m_fboId && m_rboId) )
    return;

  glDeleteFramebuffersEXT(1, &m_fboId);
  glDeleteRenderbuffersEXT(1, &m_rboId);

  m_fboId = m_rboId = 0;
  m_isOK = false;
}

void OpenGLRenderTarget::Begin()
{
  // adjust viewport and projection matrix to texture dimension
#ifdef _DEBUG
  g_renderer->CheckError();
#endif

  glViewport(0, 0, m_width, m_height);
  g_renderer->PushMatrix(Renderer::MATRIX_PROJECTION);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();

  gluPerspective(60.0f, (float)(m_width)/m_height, 0.1f, 100.0f);

  g_renderer->PushMatrix(Renderer::MATRIX_MODELVIEW);
  glMatrixMode(GL_MODELVIEW);

  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_fboId);
  //glPushAttrib(GL_VIEWPORT_BIT | GL_COLOR_BUFFER_BIT);
//   // check FBO status
//   bool status = checkFramebufferStatus();
//   if(!status)
//     ErrorMsg("Error while creating FBO");
  glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);
}

void OpenGLRenderTarget::End()
{
  //glPopAttrib();
  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);

  g_renderer->PopMatrix(Renderer::MATRIX_PROJECTION);
  g_renderer->PopMatrix(Renderer::MATRIX_MODELVIEW);
  glViewport(0, 0, 
    GLsizei(g_App->GetCamera()->GetViewportSize().x),
    GLsizei(g_App->GetCamera()->GetViewportSize().y));

  //m_GLTexture->Begin();
}

void OpenGLRenderTarget::Download(Image* img)
{
  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_fboId);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

  glReadBuffer(GL_COLOR_ATTACHMENT0_EXT); 
 
  unsigned char* pixels = img->m_data;
  glReadPixels(0, 0, m_GLTexture->GetWidth(), m_GLTexture->GetHeight(), 
    OpenGLTexture::GetOpenGLPixelFormat(img->GetPixelFormat()), 
    GL_UNSIGNED_BYTE, pixels); 

  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
}

void OpenGLRenderTarget::Download(STRING filename, ImageFileFormat iff)
{
  Image* img1 = ImageManager::InstanceRef().CreateImage("", 
    m_width, m_height, m_texture->GetPixelFormat() );
  Download(img1);
  ImageManager::InstanceRef().SaveToFile(img1, filename, iff);
}
